﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Serialization.Formatters;
using System.Windows.Forms;
using System.Xml.Serialization;
using System.Linq;
using Interfaces;


// ReSharper disable InconsistentNaming
// ReSharper disable LocalizableElement
namespace Peer
{
    public partial class PeerForm : Form
    {
        private Peer _me;
        private delegate void PutTextDelegate(string txt);
        private delegate void PutTextArrayDelegate(string[] col);

        public PeerForm()
        {
            InitializeComponent();
        }

        private void PeerForm_Load(object sender, EventArgs e)
        {
            BringToFront();

            if (_me == null)
            {
                DialogResult res;
                do
                {
                    res = openArticleList.ShowDialog();
                } while (res != DialogResult.OK);

                try
                {
                    var serializer = new XmlSerializer(typeof(List<Article>));
                
                    // Create a new file stream for reading the XML file
                    var readFileStream = new FileStream(openArticleList.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);

                    // Load the object saved above by using the Deserialize function
                    var articles = (List<Article>)serializer.Deserialize(readFileStream);

                    readFileStream.Close();

                    _me = new Peer(articles, this, txtSubject.Text);

                    foreach (var article in articles)
                    {
                        txtArticles.AppendText(article.Title);

                        foreach (var author in article.Authors)
                            txtArticles.AppendText(String.Format("|{0}", author));

                        txtArticles.AppendText("|" + article.YearOfPublication + "\n");
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Erro ao ler o ficheiro XML com os arigos.\n"+ex.Message, "Erro ficheiro XML",
                                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        #region EVENTOS SOBRE CONTROLOS GRÁFICOS
        private void btnConnect_Click(object sender, EventArgs e)
        {
            TcpChannel chnl = null;
            try
            {
                // TODO falta tratar as exceções!
                if (!String.IsNullOrEmpty(txtSubject.Text) && !String.IsNullOrEmpty(txtName.Text))
                {
                    _me.Name = txtName.Text = txtName.Text.Trim();
                    _me.Subject = txtSubject.Text.Trim();
                    
                    IDictionary props = new Hashtable();
                    props["port"] = 0;
                    chnl = new TcpChannel(props, new BinaryClientFormatterSinkProvider(),
                                                     new BinaryServerFormatterSinkProvider { TypeFilterLevel = TypeFilterLevel.Full });
                    ChannelServices.RegisterChannel(chnl, false);

                    // Se SuperPeer URI estiver preenchido significa que vai ser Peer
                    if (!String.IsNullOrEmpty(txtUri.Text))
                    {
                        _me.SetSuperPeer((ISuperPeer)Activator.GetObject(typeof(ISuperPeer), txtUri.Text), txtSubject.Text);
                        //_me.Url = _me.Name + ".rem";
                    }
                    else
                    {
                        // Significa que este Peer vai ser SuperPeer
                        //RemotingConfiguration.Configure("peer.exe.config", false); // tentar usar diferentes ficheiros, tendo em conta o nome
                        //RemotingConfiguration.RegisterWellKnownServiceType(typeof(Peer), _me.Name+".rem", WellKnownObjectMode.Singleton);
                        //_me.Url = RemotingConfiguration.GetRegisteredWellKnownServiceTypes()[0].ObjectUri;
                        //RemotingServices.Marshal(_me, _me.Url);
                        _me.PromoteToSuperPeer();
                    }

                    // Actualiza a lista de propriedades do Peer
                    UpdatePropertiesTextBox();

                    // Alterar o nome do botão de Conectar para Desconectar e actualizar o evento de onClick para o método de Disconnect
                    ChangeConnectDisconnectButton();

                    btnPromote.Enabled = !_me.IsSuperPeer;
                    txtSearchSubject.Enabled = true;
                }
                else
                {
                    MessageBox.Show("Tem que indicar o assunto de especialização e o nome deste Peer", "Erro", 
                        MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
            catch(RemotingException ex)
            {
                if (chnl != null) ChannelServices.UnregisterChannel(chnl);
                MessageBox.Show("Erro ao obter ligação com o SuperPeer indicado.\n"+ex, "Erro ao ligar", 
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            _me.Disconnect();
            // Alterar o nome do botão de Desconectar para Conectar e actualizar o evento de onClick para o método de Connect
            ChangeConnectDisconnectButton();

            // Para que possa ser executada nova ligação, é necessário efectuar o unregister do canal tcp.
            ChannelServices.UnregisterChannel(ChannelServices.GetChannel("tcp"));

            txtSearchSubject.Enabled = false;
            btnSearchSubject.Enabled = false;

            // limpar caixa das propriedades
            txtProperties.Clear();
        }

        private void btnPromote_Click(object sender, EventArgs e)
        {
            btnPromote.Enabled = false;
            try
            {
                _me.PromoteToSuperPeer();
            }
            catch (RemotingException ex)
            {
                MessageBox.Show("Erro ao tentar promover o Peer a SuperPeer.\n"+ex.Message, "Erro",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                btnPromote.Enabled = true;
            }
            // Actualiza a lista de propriedades do Peer
            UpdatePropertiesTextBox();
        }

        private void btnSearchSubject_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(txtSubject.Text))
            {
                MessageBox.Show("Tem que preencher o assunto antes de iniciar uma pesquisa.", "Erro ao iniciar pesquisa",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else
            {
                try
                {
                    // limpar box com peers encontrados na pesquisa anterior
                    lbPeersFound.Items.Clear();

                    string subject = txtSearchSubject.Text;
                    IDictionary<string, IPeer> peers = _me.GetPeersFor(subject);
                    
                    // Actualiza as caixas com os Peers encontrados
                    UpdateFoundPeersListBoxByCallBack(peers.Keys.ToArray());
                    UpdateKnownPeersListBoxByCallBack(subject);
                } 
                catch(RemotingException ex)
                {
                    MessageBox.Show("Erro ao iniciar a pesquisa por outros Peers.\n" + ex.Message, 
                                    "Erro ao iniciar pesquisa",
                                    MessageBoxButtons.OK, 
                                    MessageBoxIcon.Error);
                }
            }
        }

        private void txtSearchSubject_TextChanged(object sender, EventArgs e)
        {
            btnSearchSubject.Enabled = !String.IsNullOrEmpty(txtSearchSubject.Text);
        }
        
        private void lbPeers_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Get the currently selected item in the ListBox.
            if(lbPeers.SelectedItem != null)
            {
                string curItem = lbPeers.SelectedItem.ToString();
                string[] subjectAndPeerName = curItem.Split('-');
                try
                {

                    IEnumerable<Article> articles = _me.KnownPeers[subjectAndPeerName[0].Trim()]
                                                         [subjectAndPeerName[1].Trim()]
                                                         .GetArticles() as IEnumerable<Article>;

                    UpdateArticlesTextBox(articles);
                }
                catch(RemotingException ex)
                {
                    MessageBox.Show(string.Format("Erro ao tentar obter os artigos de {0}. Impossível obter ligação com o destino.\n{1}",
                        subjectAndPeerName[1].Trim(), ex.Message));
                }
            }
        }
        #endregion EVENTOS SOBRE CONTROLOS GRÁFICOS

        #region CALLBACKS
        public void UpdateKnownPeersListBoxByCallBack(string subject)
        {
            if(InvokeRequired)
            {
                PutTextDelegate del = UpdateKnownPeersListBoxByCallBack;
                var args = new object[] { subject };
                Invoke(del, args);
            }else
            {
                lbPeers.Items.Clear();
                foreach (var subjectPeers in _me.KnownPeers)
                    foreach (var peer in subjectPeers.Value.Keys)
                        lbPeers.Items.Add(subjectPeers.Key + " - " + peer);
            }
        }

        public void UpdateFoundPeersListBoxByCallBack(string[] newPeersFound)
        {
            if (newPeersFound == null || !newPeersFound.Any())
                return;

            if(InvokeRequired)
            {
                PutTextArrayDelegate del = UpdateFoundPeersListBoxByCallBack;
                var args = new object[] {newPeersFound};
                Invoke(del, args);
            }else
            {
                lbPeersFound.Items.Clear();
                lbPeersFound.Items.AddRange(newPeersFound);
            }
        }

        public void UpdateRequestsTextBoxByCallBack(string text)
        {
            if (InvokeRequired)
            {
                PutTextDelegate del = UpdateRequestsTextBoxByCallBack;
                var args = new object[] { text };
                Invoke(del, args);
            }
            else txtRequests.AppendText(text + "\r\n");
        }
        #endregion CALLBACKS

        #region MÉTODOS AUXILIARES
        private void UpdatePropertiesTextBox()
        {
            // TODO falta concorrência na atualização dos controlos gráficos!! 
            txtProperties.Clear();
            txtProperties.AppendText("Name: " + _me.Name + "\n");
            txtProperties.AppendText("Is SuperPeer: " + _me.IsSuperPeer + "\n");
            // TODO está um pedido remoto a mais!!!!
            txtProperties.AppendText("Is connected with: " + _me.SuperPeer.Url + "\n");
            if (_me.IsSuperPeer)
            {
                //txtProperties.AppendText("URL: " + _me.Url);
                string[] urls = ChannelServices.GetUrlsForObject(_me);
                txtProperties.AppendText("URL: " + ((urls == null || urls.Length == 0) ? _me.Name : urls[0]));
            }
        }

        private void ChangeConnectDisconnectButton()
        {
            if (btnConnect.Text.Equals("Conectar"))
            {
                btnConnect.Click -= btnConnect_Click; btnConnect.Click += btnDisconnect_Click;
                btnConnect.Text = "Desconectar";
            }
            else
            {
                btnConnect.Click += btnConnect_Click; btnConnect.Click -= btnDisconnect_Click;
                btnConnect.Text = "Conectar";
            }
        }

        private void UpdateArticlesTextBox(IEnumerable<Article> articles)
        {
            foreach (var article in articles)
            {
                txtArticles.AppendText(article.Title);

                foreach (var author in article.Authors)
                    txtArticles.AppendText(String.Format("|{0}", author));

                txtArticles.AppendText("|" + article.YearOfPublication + "\n");
            }
        }
        #endregion MÉTODOS AUXILIARES

    }
}
